1 using System;
2 using UnityEngine;
3
4 namespace UnityStandardAssets.ImageEffects
5 {
6 [ExecuteInEditMode]
7 [RequireComponent(typeof (Camera))]
8 [AddComponentMenu("Image Effects/Color Adjustments/Tonemapping")]
9 public class Tonemapping : PostEffectsBase
10 {
11 public enum TonemapperType
12 {
13 SimpleReinhard,
14 UserCurve,
15 Hable,
16 Photographic,
17 OptimizedHejiDawson,
18 AdaptiveReinhard,
19 AdaptiveReinhardAutoWhite,
20 };
21
22 public enum AdaptiveTexSize
23 {
24 Square16 = 16,
25 Square32 = 32,
26 Square64 = 64,
27 Square128 = 128,
28 Square256 = 256,
29 Square512 = 512,
30 Square1024 = 1024,
31 };
32
33 public TonemapperType type = TonemapperType.Photographic;
34 public AdaptiveTexSize adaptiveTextureSize = AdaptiveTexSize.Square256;
35
36 // CURVE parameter
37 public AnimationCurve remapCurve;
38 private Texture2D curveTex = null;
39
40 // UNCHARTED parameter
41 public float exposureAdjustment = 1.5f;
42
43 // REINHARD parameter
44 public float middleGrey = 0.4f;
45 public float white = 2.0f;
46 public float adaptionSpeed = 1.5f;
47
48 // usual & internal stuff
49 public Shader tonemapper = null;
50 public bool validRenderTextureFormat = true;
51 private Material tonemapMaterial = null;
52 private RenderTexture rt = null;
53 private RenderTextureFormat rtFormat = RenderTextureFormat.ARGBHalf;
54
55
56 public override bool CheckResources()
57 {
58 CheckSupport(false, true);
59
60 tonemapMaterial = CheckShaderAndCreateMaterial(tonemapper, tonemapMaterial);
61 if (!curveTex && type == TonemapperType.UserCurve)
62 {
63 curveTex = new Texture2D(256, 1, TextureFormat.ARGB32, false, true);
64 curveTex.filterMode = FilterMode.Bilinear;
65 curveTex.wrapMode = TextureWrapMode.Clamp;
66 curveTex.hideFlags = HideFlags.DontSave;
67 }
68
69 if (!isSupported)
70 ReportAutoDisable();
71 return isSupported;
72 }
73
74
75 public float UpdateCurve()
76 {
77 float range = 1.0f;
78 if (remapCurve.keys.Length < 1)
79 remapCurve = new AnimationCurve(new Keyframe(0, 0), new Keyframe(2, 1));
80 if (remapCurve != null)
81 {
82 if (remapCurve.length > 0)
83 range = remapCurve[remapCurve.length - 1].time;
84 for (float i = 0.0f; i <= 1.0f; i += 1.0f/255.0f)
85 {
86 float c = remapCurve.Evaluate(i*1.0f*range);
87 curveTex.SetPixel((int) Mathf.Floor(i*255.0f), 0, new Color(c, c, c));
88 }
89 curveTex.Apply();
90 }
91 return 1.0f/range;
92 }
93
94
95 private void OnDisable()
96 {
97 if (rt)
98 {
99 DestroyImmediate(rt);
100 rt = null;
101 }
102 if (tonemapMaterial)
103 {
104 DestroyImmediate(tonemapMaterial);
105 tonemapMaterial = null;
106 }
107 if (curveTex)
108 {
109 DestroyImmediate(curveTex);
110 curveTex = null;
111 }
112 }
113
114
115 private bool CreateInternalRenderTexture()
116 {
117 if (rt)
118 {
119 return false;
120 }
121 rtFormat = SystemInfo.SupportsRenderTextureFormat(RenderTextureFormat.RGHalf) ? RenderTextureFormat.RGHalf : RenderTextureFormat.ARGBHalf;
122 rt = new RenderTexture(1, 1, 0, rtFormat);
123 rt.hideFlags = HideFlags.DontSave;
124 return true;
125 }
126
127
128 // attribute indicates that the image filter chain will continue in LDR
129 [ImageEffectTransformsToLDR]
130 private void OnRenderImage(RenderTexture source, RenderTexture destination)
131 {
132 if (CheckResources() == false)
133 {
134 Graphics.Blit(source, destination);
135 return;
136 }
137
138 #if UNITY_EDITOR
139 validRenderTextureFormat = true;
140 if (source.format != RenderTextureFormat.ARGBHalf)
141 {
142 validRenderTextureFormat = false;
143 }
144 #endif
145
146 // clamp some values to not go out of a valid range
147
148 exposureAdjustment = exposureAdjustment < 0.001f ? 0.001f : exposureAdjustment;
149
150 // SimpleReinhard tonemappers (local, non adaptive)
151
152 if (type == TonemapperType.UserCurve)
153 {
154 float rangeScale = UpdateCurve();
155 tonemapMaterial.SetFloat("_RangeScale", rangeScale);
156 tonemapMaterial.SetTexture("_Curve", curveTex);
157 Graphics.Blit(source, destination, tonemapMaterial, 4);
158 return;
159 }
160
161 if (type == TonemapperType.SimpleReinhard)
162 {
163 tonemapMaterial.SetFloat("_ExposureAdjustment", exposureAdjustment);
164 Graphics.Blit(source, destination, tonemapMaterial, 6);
165 return;
166 }
167
168 if (type == TonemapperType.Hable)
169 {
170 tonemapMaterial.SetFloat("_ExposureAdjustment", exposureAdjustment);
171 Graphics.Blit(source, destination, tonemapMaterial, 5);
172 return;
173 }
174
175 if (type == TonemapperType.Photographic)
176 {
177 tonemapMaterial.SetFloat("_ExposureAdjustment", exposureAdjustment);
178 Graphics.Blit(source, destination, tonemapMaterial, 8);
179 return;
180 }
181
182 if (type == TonemapperType.OptimizedHejiDawson)
183 {
184 tonemapMaterial.SetFloat("_ExposureAdjustment", 0.5f*exposureAdjustment);
185 Graphics.Blit(source, destination, tonemapMaterial, 7);
186 return;
187 }
188
189 // still here?
190 // => adaptive tone mapping:
191 // builds an average log luminance, tonemaps according to
192 // middle grey and white values (user controlled)
193
194 // AdaptiveReinhardAutoWhite will calculate white value automagically
195
196 bool freshlyBrewedInternalRt = CreateInternalRenderTexture(); // this retrieves rtFormat, so should happen before rt allocations
197
198 RenderTexture rtSquared = RenderTexture.GetTemporary((int) adaptiveTextureSize, (int) adaptiveTextureSize, 0, rtFormat);
199 Graphics.Blit(source, rtSquared);
200
201 int downsample = (int) Mathf.Log(rtSquared.width*1.0f, 2);
202
203 int div = 2;
204 var rts = new RenderTexture[downsample];
205 for (int i = 0; i < downsample; i++)
206 {
207 rts[i] = RenderTexture.GetTemporary(rtSquared.width/div, rtSquared.width/div, 0, rtFormat);
208 div *= 2;
209 }
210
211 // downsample pyramid
212
213 var lumRt = rts[downsample - 1];
214 Graphics.Blit(rtSquared, rts[0], tonemapMaterial, 1);
215 if (type == TonemapperType.AdaptiveReinhardAutoWhite)
216 {
217 for (int i = 0; i < downsample - 1; i++)
218 {
219 Graphics.Blit(rts[i], rts[i + 1], tonemapMaterial, 9);
220 lumRt = rts[i + 1];
221 }
222 }
223 else if (type == TonemapperType.AdaptiveReinhard)
224 {
225 for (int i = 0; i < downsample - 1; i++)
226 {
227 Graphics.Blit(rts[i], rts[i + 1]);
228 lumRt = rts[i + 1];
229 }
230 }
231
232 // we have the needed values, let's apply adaptive tonemapping
233
234 adaptionSpeed = adaptionSpeed < 0.001f ? 0.001f : adaptionSpeed;
235 tonemapMaterial.SetFloat("_AdaptionSpeed", adaptionSpeed);
236
237 rt.MarkRestoreExpected(); // keeping luminance values between frames, RT restore expected
238
239 #if UNITY_EDITOR
240 if (Application.isPlaying && !freshlyBrewedInternalRt)
241 Graphics.Blit(lumRt, rt, tonemapMaterial, 2);
242 else
243 Graphics.Blit(lumRt, rt, tonemapMaterial, 3);
244 #else
245 Graphics.Blit (lumRt, rt, tonemapMaterial, freshlyBrewedInternalRt ? 3 : 2);
246 #endif
247
248 middleGrey = middleGrey < 0.001f ? 0.001f : middleGrey;
249 tonemapMaterial.SetVector("_HdrParams", new Vector4(middleGrey, middleGrey, middleGrey, white*white));
250 tonemapMaterial.SetTexture("_SmallTex", rt);
251 if (type == TonemapperType.AdaptiveReinhard)
252 {
253 Graphics.Blit(source, destination, tonemapMaterial, 0);
254 }
255 else if (type == TonemapperType.AdaptiveReinhardAutoWhite)
256 {
257 Graphics.Blit(source, destination, tonemapMaterial, 10);
258 }
259 else
260 {
261 Debug.LogError("No valid adaptive tonemapper type found!");
262 Graphics.Blit(source, destination); // at least we get the TransformToLDR effect
263 }
264
265 // cleanup for adaptive
266
267 for (int i = 0; i < downsample; i++)
268 {
269 RenderTexture.ReleaseTemporary(rts[i]);
270 }
271 RenderTexture.ReleaseTemporary(rtSquared);
272 }
273 }
274 }